﻿using System;
using System.Text;
using Velocity4Net.Errors;
using Velocity4Net.Util;

namespace Velocity4Net.Runtime.Parser
{

    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */




    /**
     * This is an extension of the ParseException, which also takes a
     * template name.
     *
     * @see org.apache.velocity.runtime.parser.ParseException
     *
     * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a>
     * @version $Id: TemplateParseException.java 1855206 2019-03-11 11:09:53Z cbrisson $
     * @since 1.5
     */
    public class TemplateParseException
           : ParseException
           , ExtendedParseException
    {
        /**
         * This is the name of the template which contains the parsing error, or
         * null if not defined.
         */
        private readonly String templateName;

        /**
         * This constructor is used to add a template name
         * to info cribbed from a ParseException generated in the parser.
         * @param currentTokenVal
         * @param expectedTokenSequencesVal
         * @param tokenImageVal
         * @param templateNameVal
         */
        public TemplateParseException(Token currentTokenVal, int[][] expectedTokenSequencesVal, String[] tokenImageVal,
            String templateNameVal) : base(currentTokenVal, expectedTokenSequencesVal, tokenImageVal)
        {
            this.templateName = templateNameVal;
        }

        /**
         * <p>This constructor is used by the method "generateParseException"
         * in the generated parser.  Calling this constructor generates
         * a new object of this type with the fields "currentToken",
         * "expectedTokenSequences", and "tokenImage" set.  The bool
         * flag "specialConstructor" is also set to true to indicate that
         * this constructor was used to create this object.
         * This constructor calls its super class with the empty string
         * to force the "toString" method of parent class "Throwable" to
         * print the error message in the form:</p>
         * <pre>
         *     ParseException: &lt;result of getMessage&gt;
         * </pre>
         * @param currentTokenVal
         * @param expectedTokenSequencesVal
         * @param tokenImageVal
         */
        public TemplateParseException(Token currentTokenVal, int[][] expectedTokenSequencesVal, String[] tokenImageVal)
            : base(currentTokenVal, expectedTokenSequencesVal, tokenImageVal)
        {
            templateName = "*unset*";
        }

        /**
         * The following constructors are for use by you for whatever
         * purpose you can think of.  Constructing the exception in this
         * manner makes the exception behave in the normal way - i.e., as
         * documented in the class "Throwable".  The fields "errorToken",
         * "expectedTokenSequences", and "tokenImage" do not contain
         * relevant information.  The JavaCC generated code does not use
         * these constructors.
         */
        public TemplateParseException() : base()
        {
            templateName = "*unset*";
        }

        /**
         * Creates a new TemplateParseException object.
         *
         * @param message TODO: DOCUMENT ME!
         */
        public TemplateParseException(String message) : base(message)
        {
            templateName = "*unset*";
        }


        /**
         * returns the Template name where this exception occurred.
         * @return The Template name where this exception occurred.
         */
        public String getTemplateName()
        {
            return templateName;
        }

        /**
         * returns the line number where this exception occurred.
         * @return The line number where this exception occurred.
         */
        public int getLineNumber()
        {
            if ((currentToken != null) && (currentToken.next != null))
            {
                return currentToken.next.beginLine;
            }
            else
            {
                return -1;
            }
        }

        /**
         * returns the column number where this exception occurred.
         * @return The column number where this exception occurred.
         */
        public int getColumnNumber()
        {
            if ((currentToken != null) && (currentToken.next != null))
            {
                return currentToken.next.beginColumn;
            }
            else
            {
                return -1;
            }
        }

        /**
         * This method has the standard behavior when this object has been
         * created using the standard constructors.  Otherwise, it uses
         * "currentToken" and "expectedTokenSequences" to generate a parse
         * error message and returns it.  If this object has been created
         * due to a parse error, and you do not catch it (it gets thrown
         * from the parser), then this method is called during the printing
         * of the final stack trace, and hence the correct error message
         * gets displayed.
         * @return The error message.
         */
        public String getMessage()
        {
            if (!specialConstructor)
            {
                StringBuilder sb = new StringBuilder(Message);
                appendTemplateInfo(sb);
                return sb.ToString();
            }

            int maxSize = 0;

            StringBuilder expected = new StringBuilder();

            foreach (int[] expectedTokenSequence in expectedTokenSequences)
            {
                if (maxSize < expectedTokenSequence.Length)
                {
                    maxSize = expectedTokenSequence.Length;
                }

                for (int j = 0; j < expectedTokenSequence.Length; j++)
                {
                    expected.Append(tokenImage[expectedTokenSequence[j]]).Append(" ");
                }

                if (expectedTokenSequence[expectedTokenSequence.Length - 1] != 0)
                {
                    expected.Append("...");
                }

                expected.Append(eol).Append("    ");
            }

            StringBuilder retval = new StringBuilder("Encountered \"");
            Token tok = currentToken.next;

            for (int i = 0; i < maxSize; i++)
            {
                if (i != 0)
                {
                    retval.Append(" ");
                }

                if (tok.kind == 0)
                {
                    retval.Append(tokenImage[0]);
                    break;
                }

                retval.Append(add_escapes(tok.image));
                tok = tok.next;
            }

            retval.Append("\" at ");
            appendTemplateInfo(retval);

            if (expectedTokenSequences.Length == 1)
            {
                retval.Append("Was expecting:").Append(eol).Append("    ");
            }
            else
            {
                retval.Append("Was expecting one of:").Append(eol).Append("    ");
            }

            // avoid JDK 1.3 StringBuffer.Append(Object o) vs 1.4 StringBuffer.Append(StringBuffer sb) gotcha.
            retval.Append(expected.ToString());
            return retval.ToString();
        }

        /**
         * @param sb
         */
        protected void appendTemplateInfo(StringBuilder sb)
        {
            sb.Append(StringUtils.formatFileString(getTemplateName(), getLineNumber(), getColumnNumber()));
            sb.Append(eol);
        }
    }
}